iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0

這篇主要是講到有關 Python 中很重要的 import,因為如果後面在做大型專案時,常常需要 import 其他模組或套件進來,而 Python 的 import 比較多眉角需要注意。雖然不長但有點難理解,不過知道了之後超(少)級(踩)的(很)好(多)用(雷),所以把它獨立出來做一篇了。

不過要說到進階的 import 就要先說到 Module(模組) 跟 Package(套件) 的差異,每個 Module(模組) 其實就是一個 .py 檔(忘記 .py 檔是啥地去重看 Day 03 ),裡面有功能相近的 Function(函式) ( Day 03 一樣有講到);一個 Package(套件) 就是裡面有一堆(可有可無) Module(模組) 與一個 __init__.py 檔的一個資料夾,大概會像這樣:

sample_package  # 它就是 Package
├── __init__.py  # Package 裡面一定要有的,裡面可以為空,也可以填一些初始化的東西。
└── sample_module.py

sample_module.py

# Module 裡的 Function
def sample_function():
    # Do something

大概結構會向上面一樣,而 import 分為 Absolute imoprt(絕對導入)Explicit relative import(顯式相對導入) 這兩種類型,說到這裡,還記得 Day 02 講到的 PEP8 嗎?在那個 Coding Style 裡面有關於 import 的規定及建議。

那麼先看看關於 Absolute imoprt(絕對導入) 吧。PEP8 中是這麼寫的(有點長,不想看可以跳到後面沒關係):

Absolute imports are recommended, as they are usually more readable and tend to be better behaved (or at least give better error messages) if the import system is incorrectly configured (such as when a directory inside a package ends up on sys.path)

建議使用絕對導入,因為如果導入系統配置不正確(例如當包內的目錄最終位於 sys.path 上時),它們通常更具可讀性並且往往表現更好(或至少給出更好的錯誤消息)

好的,我們知道了 Absolute imoprt(絕對導入) 是比較建議的寫法,那 Explicit relative import(顯式相對導入) 又如何呢?

Explicit relative imports are an acceptable alternative to absolute imports, especially when dealing with complex package layouts where using absolute imports would be unnecessarily verbose.

顯式相對導入是絕對導入的可接受替代方案,尤其是在處理複雜的套件佈局時,使用絕對導入會不必要地冗長

好的,我們又知道了 Explicit relative import(顯式相對導入) 也是可以接受的寫法,尤其是在處理複雜的套件時。不過既然有顯式,那有沒有隱式呢?其實原本是有的(對,現在沒有,正確地說是在 Python3 中沒有),為什麼好端端的就不見了呢?PEP8中也有解釋。

Implicit relative imports should never be used and have been removed in Python 3.

不應使用隱式相對導入,並且已在 Python 3 中刪除。

那 Absolute imoprt(絕對導入) 跟 Explicit relative import(顯式相對導入) 有什麼不一樣呢?

Absolute imoprt(絕對導入) & Explicit relative import(顯式相對導入)

同一個 Package

現在假設要做一個專案,你寫好了一個模組(例如對資料庫操作的模組) module_x.py ,跟一部分功能(例如註冊、登入、登出) module_a.py (雖然放一起感覺怪怪的,但先不要理它),結構長這樣:

package_1
├── __init__.py  # 它是空的
├── module_a.py  # 模組
└── module_x.py  # 功能

package_1/module_x.py 長這樣

def function_x():
    # Do something

module_a.py 要導入 package_1 裡的 module_x.py 使用,絕對導入跟顯式相對導入要怎麼寫呢?

Absolute imoprt(絕對導入)

module_a.py

from package_1.module_x import function_x
# or
from package_1 import module_x

def function_a():
    # Do function_x
    function_x()

Explicit relative import(顯式相對導入)

module_a.py

from .module_x import function_x
# or
from . import module_x

def function_a():
    # Do function_x
    function_x()

94這樣,看出差別了嗎?

不同 Package

如果之後想把模組跟功能分開放,並且增加一個新模組,就建立另一個 Package 把他們分開。分開後長這樣:

package
├── __init__.py  # 它是空的
├── subpackage_1
│   ├── __init__.py  # 它是空的
│   └── module_x.py  # 模組,它不動
├── subpackage_2
│   ├── __init__.py  # 它是空的
│   └── module_y.py  # 模組,新增的另一個模組
└── module_a.py  # 功能,它搬過來

假設 module_y.py 模組要使用到 module_x.py 模組要怎麼引入呢?

Absolute imoprt(絕對導入)

module_y.py

from package.subpackage_1.module_x import function_x
# or 
from package.subpackage_1 import module_x

def function_a():
    # Do function_x
    function_x()

Explicit relative import(顯式相對導入)

module_y.py

from .subpackage_1.module_x import function_x
# or 
from .subpackage_1 import module_x

def function_a():
    # Do function_x
    function_x()

說到這裡,應該對 import 有更進一步的認識了吧!

要注意的點

最後是有一些要注意的東西

  1. 包含顯式相對導入的檔案不能直接執行,只能作為 module 被引用。

    直接執行

     $ python abc/xyz.py
    

    沒有辦法直接執行可是又一定要執行怎麼辦,全部改絕對導入又很麻煩,那怎麼辦呢?

    預先 import 再執行。

     $ python -m abc.xyz
    

    這樣執行就類似先 import abc.xyz 在執行它,這樣就可以了。

  2. Circular Import(循環導入)

    什麼是循環導入呢?就是A導入了B,然後B又導入A,這樣 Python 會報錯。

    如果真的必須這樣又有甚麼解決方式呢?可以透過不要導入整個模組,只導入裡面的 Class or Function 就可以避免。

參考資料

Python 的 Import 陷阱

那麼就大概這樣,這篇是讓你在之後較大的專案可以更靈活的 import 而已。

大家掰~掰~


上一篇
Day 07 PIP & import 入門
下一篇
Day 09 pipenv
系列文
月光下的Flask之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
jiamingla
iT邦新手 4 級 ‧ 2022-03-08 01:16:56

如果之後想把模組跟功能分開放,並且增加一個新模組,就建立另一個 Package 把牠們分開。分開後長這樣:

牠們,字打錯了

shadow_gold iT邦研究生 2 級 ‧ 2022-03-08 09:20:09 檢舉

十分感謝您的指正,希望這系列文章能夠幫助到您。

我要留言

立即登入留言